/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file -- DO NOT EDIT!

static std::vector<uint8_t> &operator<<(std::vector<uint8_t> &out, Opcode op)
{
    if (static_cast<unsigned>(op) >= <%= Panda.instructions.select(&:prefix).map(&:opcode_idx).min %>) {
        out.push_back(static_cast<uint8_t>(op));
        out.push_back(static_cast<uint8_t>(static_cast<unsigned>(op) >> 8));
    } else {
        out.push_back(static_cast<uint8_t>(op));
    }
    return out;
}

#ifdef WITH_MOCK
#include <bytecode_emitter.cpp>
#endif

template <BytecodeInstruction::Format format, typename It, typename... Types>
static size_t Emit(It out, Types... args)
{
    size_t insn_len = 0;

% insns_uniq_sort_fmts.each do |i| # Panda::formats.each do |fmt|
%   fmt = i.format
%   ops = i.operands
%   offsets = [0]  # for opcode
%   offsets += ops.map(&:offset).sort
%   offsets += [fmt.size * 8]
%
    if constexpr (format == BytecodeInstruction::Format::<%= fmt.pretty.upcase %>) {
        constexpr size_t SIZE = <%= fmt.size %>;
        constexpr std::array<uint8_t, <%= offsets.size %>> OFF{<%= offsets.join(', ') %>};
        static_assert(OFF.size() == sizeof...(args) + 1);
        std::array<uint8_t, SIZE> buf{};
        Span<uint8_t> buf_sp(buf);
        Span<const uint8_t> off_sp(OFF);
        EmitImpl(buf_sp, off_sp, args...);
        std::copy(buf.begin(), buf.end(), out);
        insn_len = SIZE;
    }

% end
    return insn_len;
}

static constexpr uint8_t DEFAULT_OFFSET = 4U;
static constexpr std::array<uint8_t, 4U> OFFSETS{8, 16, 32, 64};

static std::vector<std::vector<uint8_t>> MakeOffsetsVector(uint8_t current)
{
    std::vector<std::vector<uint8_t>> res;
    std::vector<uint8_t> init;
    init.reserve(current);
    for (uint8_t i = 0U; i < current; i++) {
        init.emplace_back(i * DEFAULT_OFFSET);
    }
    res.emplace_back(init);
    for (uint8_t i = 0U; i < 4U; i++) {
        for (uint8_t j = 0U; j < current - 1; j++) {
            std::vector<uint8_t> tmp;
            tmp.reserve(current);
            uint8_t start = 0U;
            for (uint8_t z = 0U; z < current; z++) {
                tmp.emplace_back(start);
                if (j == z) {
                    start += OFFSETS[i];
                } else {
                    start += DEFAULT_OFFSET;
                }
            }
            res.emplace_back(tmp);
        }
    }
    return res;
}

template <BytecodeInstruction::Format format, typename It, typename... Types>
static size_t EmitMock(It out, Types... args)
{
    size_t insn_len = 0;

% insns_uniq_sort_fmts.each do |i| # Panda::formats.each do |fmt|
%   fmt = i.format
%   ops = i.operands
%   if fmt.size == 1
%     offsets4 = [0]  # for opcode
%     offsets4 += [4]
%     offsets8 = [0]  # for opcode
%     offsets8 += [8]
%     offsets16 = [0]  # for opcode
%     offsets16 += [16]
%     offsets32 = [0]  # for opcode
%     offsets32 += [32]
%     offsets64 = [0]  # for opcode
%     offsets64 += [64]

    if constexpr (format == BytecodeInstruction::Format::<%= fmt.pretty.upcase %>) {
        constexpr size_t SIZE = <%= fmt.size %>;
        constexpr std::array<uint8_t, <%= offsets4.size %>> OFF4{<%= offsets4.join(', ') %>};
        constexpr std::array<uint8_t, <%= offsets8.size %>> OFF8{<%= offsets8.join(', ') %>};
        constexpr std::array<uint8_t, <%= offsets16.size %>> OFF16{<%= offsets16.join(', ') %>};
        constexpr std::array<uint8_t, <%= offsets32.size %>> OFF32{<%= offsets32.join(', ') %>};
        constexpr std::array<uint8_t, <%= offsets64.size %>> OFF64{<%= offsets64.join(', ') %>};
        static_assert(OFF4.size() == sizeof...(args) + 1);
        static_assert(OFF8.size() == sizeof...(args) + 1);
        static_assert(OFF16.size() == sizeof...(args) + 1);
        static_assert(OFF32.size() == sizeof...(args) + 1);
        static_assert(OFF64.size() == sizeof...(args) + 1);
        std::array<uint8_t, SIZE * 8> buf{};
        Span<uint8_t> buf_sp(buf);
        Span<const uint8_t> off_sp4(OFF4);
        EmitImpl(buf_sp, off_sp4, args...);
        Span<const uint8_t> off_sp8(OFF8);
        EmitImpl(buf_sp, off_sp8, args...);
        Span<const uint8_t> off_sp16(OFF16);
        EmitImpl(buf_sp, off_sp16, args...);
        Span<const uint8_t> off_sp32(OFF32);
        EmitImpl(buf_sp, off_sp32, args...);
        Span<const uint8_t> off_sp64(OFF64);
        EmitImpl(buf_sp, off_sp64, args...);
        std::copy(buf.begin(), buf.end(), out);
        insn_len = SIZE;
    }
%   else

    if constexpr (format == BytecodeInstruction::Format::<%= fmt.pretty.upcase %>) {
        constexpr size_t SIZE = <%= fmt.size %>;
        std::array<uint8_t, SIZE * 8U> buf{};
        Span<uint8_t> buf_sp(buf);
        const auto &vector = MakeOffsetsVector(<%= ops.size + 2 %>);
        for (const auto &v : vector) {
            Span<const uint8_t> off_sp(v);
            EmitImpl(buf_sp, off_sp, args...);
        }
        std::copy(buf.begin(), buf.end(), out);
        insn_len = SIZE;
    }
%   end
% end

    return insn_len;
}

% OPCODE_TYPE = 'BytecodeInstruction::Opcode'
% FORMAT_TYPE = 'BytecodeInstruction::Format'
%
% def opcode_full_name(i)
%   OPCODE_TYPE + '::' + i.opcode.upcase
% end
%
% def format_full_name(i)
%   FORMAT_TYPE + '::' + i.format.pretty.upcase
% end
%
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
%   emitter_name = group.first.emitter_name
%   formats = group.map(&:format)
%   signature = emitter_signature(group, group.first.jump?)
%   signature_str = signature.map { |o| "#{o.type} #{o.name}" }.join(', ')
%

[[maybe_unused]] static void <%= emitter_name %>Mock(<%= signature_str %>)
{
    std::vector<uint8_t> bytecode;
%
%   method_args = signature.map(&:name)
%   opcodes = group.map { |i| opcode_full_name(i) }
%
%   i = group[0]
%
%   if i.jump?
%       jmp_args = [opcode_full_name(i)] + method_args[0..-2] + ["0"]
%       format = format_full_name(i)
    EmitMock<<%= format %>>(std::back_inserter(bytecode), <%= jmp_args.join(', ') %>);
%   elsif group.length() == 1
%       jmp_args = [opcode_full_name(i)] + method_args
%       format = format_full_name(i)
    EmitMock<<%= format %>>(std::back_inserter(bytecode), <%= jmp_args.join(', ') %>);
%   else
%       group.each do |i|
%           format = format_full_name(i)
%           opcode = opcode_full_name(i)
    EmitMock<<%= format %>>(std::back_inserter(bytecode), <%= opcode %>, <%= method_args.join(', ') %>);
%       end
%   end
}
% end
%
% def get_min(width, is_signed)
%   if width < 8
%       if is_signed
%           return '%d' % ((1 << (width - 1)) - (1 << width))
%       else
%           return '%d' % ((1 << (width - 1)))
%       end
%   else
%       if is_signed
%           return 'std::numeric_limits<int%d_t>::min()' % width
%       else
%           return '%s + 1' % get_max(width / 2, false)
%       end
%   end
% end
%
% def get_max(width, is_signed)
%   if width < 8
%       if is_signed
%           return '%d' % ((1 << (width - 1)) - 1)
%       else
%           return '%d' % ((1 << width) - 1)
%       end
%   else
%       if is_signed
%           return 'std::numeric_limits<int%d_t>::max()' % width
%       else
%           return 'std::numeric_limits<uint%d_t>::max()' % width
%       end
%   end
% end
%
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
%   emitter_name = group.first.emitter_name
%   formats = group.map(&:format)
%   signature = emitter_signature(group, group.first.jump?)
%   method_args = signature.map(&:name)
%
%   if emitter_name == "Jmp"
%       next
%   end
%
%   i = group.first
%
%   if i.operands.empty?

HWTEST(BytecodeEmitter, <%= emitter_name %>, testing::ext::TestSize.Level0)
{
    TestNoneFormat(Opcode::<%= i.opcode.upcase %>, [](BytecodeEmitter* emitter) {
        emitter-><%= emitter_name %>();
    });
    <%= emitter_name %>Mock();
}
%   elsif i.jump? && method_args.length == 1
%     group.each do |group_insn|
%       pretty_format = group_insn.format.pretty.upcase
%       opcode = group_insn.opcode.upcase

HWTEST(BytecodeEmitter, <%= emitter_name %>_<%= pretty_format %>_AUTO, testing::ext::TestSize.Level0)
{
    Jmpz_<%= pretty_format %>(Opcode::<%= opcode %>, [](BytecodeEmitter* emitter, const Label &label) {
        emitter-><%= emitter_name %>(label);
        <%= emitter_name %>Mock(label);
    });
}
%     end
%   elsif i.jump? && method_args.length == 2
%     group.each do |group_insn|
%       pretty_format = group_insn.format.pretty.upcase
%       opcode = group_insn.opcode.upcase

HWTEST(BytecodeEmitter, <%= emitter_name %>_<%= pretty_format %>_AUTO, testing::ext::TestSize.Level0)
{
    Jmp_<%= pretty_format %>(Opcode::<%= opcode %>, [](BytecodeEmitter* emitter, uint8_t reg, const Label &label) {
        emitter-><%= emitter_name %>(reg, label);
        <%= emitter_name %>Mock(reg, label);
    });
}
%     end
%   else
%     group.each do |i|

HWTEST(BytecodeEmitter, <%= emitter_name %>_<%= i.format.pretty.upcase %>_AUTO, testing::ext::TestSize.Level0)
{
%           num_ops = i.operands.length()
%           ops = format_ops(i.format)
%           ['min', 'max'].repeated_permutation(num_ops).each do |p|
%               args = []
%               vals = []
%               p.each_with_index do |v, index|
%                   op = ops[index]
%                   is_signed = i.operands[index].is_signed_imm? || i.operands[index].is_float_imm?
%                   arg = v == 'min' ? get_min(op.width, is_signed) : get_max(op.width, is_signed)
%                   args.push(arg)
%                   if op.width <= 8
%                       vals.push(arg)
%                   elsif op.width == 16
%                       vals.push('Split16(%s)' % arg)
%                   elsif op.width == 32
%                       vals.push('Split32(%s)' % arg)
%                   else
%                       vals.push('Split64(%s)' % arg)
%                   end
%               end
%
%               index = 0
%               packed_vals = []
%               while index < num_ops do
%                   if ops[index].width == 4
%                       packed_vals.push('(((static_cast<uint8_t>(%s) & 0xF) << 4) | (static_cast<uint8_t>(%s) & 0xF))' % [vals[index + 1], vals[index]])
%                       index += 2
%                   else
%                       packed_vals.push(vals[index])
%                       index += 1
%                   end
%               end
%
    {
        BytecodeEmitter emitter;
        emitter.<%= emitter_name %>(<%= args.join(', ') %>);
        std::vector<uint8_t> out;
        ASSERT_EQ(BytecodeEmitter::ErrorCode::SUCCESS, emitter.Build(&out));
        std::vector<uint8_t> expected;
        expected << Opcode::<%= i.opcode.upcase %> << <%= packed_vals.join(' << ') %>;
        ASSERT_EQ(expected, out);
        <%= emitter_name %>Mock(<%= args.join(', ') %>);
    }

%           end
}
%     end
%   end
% end
